package citylife.geotools.demo;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.Transaction;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

public class Class2ReadCsvCreateShp {

  public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException, SchemaException, FileNotFoundException,
      IOException {
    // 读取csv
    File csvFile = readCsv();
    if (csvFile == null) {
      return;
    }

    // 定义FeatureType
    final SimpleFeatureType TYPE = createFeatureType(); // defType();

    // 读取CSV数据，转Feature
    List<SimpleFeature> features = createFeatures(TYPE, csvFile);

    // 创建sph文件，初始化ShapefileDataStore
    ShapefileDataStore newDataStore = createOutputShp(TYPE, csvFile);

    // 将Features写入shp文件
    writeFeaturesToShpWithTransaction(newDataStore, TYPE, features);

  }

  /**
   * csv文件选择器
   * 
   * @return
   * @throws ClassNotFoundException
   * @throws InstantiationException
   * @throws IllegalAccessException
   * @throws UnsupportedLookAndFeelException
   */
  private static File readCsv() throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
    // Set cross-platform look & feel for compatability
    UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());

    return JFileDataStoreChooser.showOpenFile("csv", null);
  }

  /**
   * 简易定义FeatureType，一般不用于生产
   * 
   * We use the DataUtilities class to create a FeatureType that will describe the data in our
   * shapefile.
   *
   * See also the createFeatureType method below for another, more flexible approach.
   */
  @SuppressWarnings("unused")
  @Deprecated
  private static SimpleFeatureType defType() throws SchemaException {
    // SRID:4326是空间特定椭圆体的ID，不同的SRID是不能联合查询的
    return DataUtilities.createType("Location", "the_geom:Point:srid=4326," + // <- the geometry attribute: Point type
        "name:String," + // <- a String attribute
        "number:Integer" // a number attribute
    );
  }

  /**
   * Here is how you can use a SimpleFeatureType builder to create the schema for your shapefile
   * dynamically.
   *
   * <p>
   * This method is an improvement on the code used in the main method above (where we used
   * DataUtilities.createFeatureType) because we can set a Coordinate Reference System for the
   * FeatureType and a a maximum field length for the 'name' field dddd
   */
  private static SimpleFeatureType createFeatureType() {

    SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
    builder.setName("Location");
    builder.setCRS(DefaultGeographicCRS.WGS84); // <- Coordinate reference system

    // add attributes in order
    builder.add("the_geom", Point.class);
    builder.length(15).add("Name", String.class); // <- 15 chars width for name field
    builder.add("number", Integer.class);
    builder.add("seqInt", Integer.class);

    // build the type
    final SimpleFeatureType LOCATION = builder.buildFeatureType();

    return LOCATION;
  }

  /**
   * 读取CSV数据，转Feature
   * 
   * 生产时如果需要读取csv文件，官网推荐使用JavaCSV，对引号等符号的读取等有比较好的解决方案
   * 
   * @param TYPE
   * @param file
   * @return
   * @throws FileNotFoundException
   * @throws IOException
   */
  private static List<SimpleFeature> createFeatures(final SimpleFeatureType TYPE, File file) throws FileNotFoundException, IOException {
    /*
     * A list to collect features as we create them.
     */
    List<SimpleFeature> features = new ArrayList<>();

    /*
     * GeometryFactory will be used to create the geometry attribute of each feature,
     * using a Point object for the location.
     */
    GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();

    SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);

    try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
      /* First line of the data file is the header */
      String line = reader.readLine();
      System.out.println("Header: " + line);

      for (line = reader.readLine(); line != null; line = reader.readLine()) {
        if (line.trim().length() > 0) { // skip blank lines
          String[] tokens = line.split("\\,");

          double latitude = Double.parseDouble(tokens[0]);
          double longitude = Double.parseDouble(tokens[1]);
          String name = tokens[2].trim();
          int number = Integer.parseInt(tokens[3].trim());

          /* Longitude (= x coord) first ! */
          Point point = geometryFactory.createPoint(new Coordinate(longitude, latitude));

          featureBuilder.add(point);
          featureBuilder.add(name);
          featureBuilder.add(number);
          featureBuilder.add(999);
          SimpleFeature feature = featureBuilder.buildFeature(null);
          features.add(feature);
        }
      }
    }
    
    // 生产时如果需要读取csv文件，官网推荐使用JavaCSV，对引号等符号的读取等有比较好的解决方案
//  <dependency>
//    <groupId>net.sourceforge.javacsv</groupId>
//    <artifactId>javacsv</artifactId>
//    <version>2.0</version>
//  </dependency>
    
    return features;
  }

  /**
   * 创建sph文件，初始化ShapefileDataStore
   * 
   * @param csvFile
   * @param TYPE
   * @return
   * @throws IOException
   */
  private static ShapefileDataStore createOutputShp(final SimpleFeatureType TYPE, File csvFile) throws IOException {
    /*
     * Get an output file name and create the new shapefile
     */
    File newFile = getNewShapeFile(csvFile);

    ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();

    Map<String, Serializable> params = new HashMap<>();
    params.put("url", newFile.toURI().toURL());
    params.put("create spatial index", Boolean.TRUE);

    ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);

    /*
     * TYPE is used as a template to describe the file contents
     */
    newDataStore.createSchema(TYPE);

    return newDataStore;
  }

  /**
   * Csv同目录下创建shp文件
   * Prompt the user for the name and path to use for the output shapefile
   *
   * @param csvFile the input csv file used to create a default shapefile name
   * @return name and path for the shapefile as a new File object
   */
  private static File getNewShapeFile(File csvFile) {
    String path = csvFile.getAbsolutePath();
    String newPath = path.substring(0, path.length() - 4) + ".shp";

    JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
    chooser.setDialogTitle("Save shapefile");
    chooser.setSelectedFile(new File(newPath));

    int returnVal = chooser.showSaveDialog(null);

    if (returnVal != JFileDataStoreChooser.APPROVE_OPTION) {
      // the user cancelled the dialog
      System.exit(0);
    }

    File newFile = chooser.getSelectedFile();
    if (newFile.equals(csvFile)) {
      System.out.println("Error: cannot replace " + csvFile);
      System.exit(0);
    }

    return newFile;
  }

  /**
   * 将Features写入shp文件
   * 
   * @param newDataStore
   * @param TYPE
   * @param features
   * @throws IOException
   */
  private static void writeFeaturesToShpWithTransaction(ShapefileDataStore newDataStore, final SimpleFeatureType TYPE, List<SimpleFeature> features) throws IOException {
    /*
     * Write the features to the shapefile
     */
    Transaction transaction = new DefaultTransaction("create");

    String typeName = newDataStore.getTypeNames()[0];
    SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
    SimpleFeatureType SHAPE_TYPE = featureSource.getSchema();
    /*
     * The Shapefile format has a couple limitations:
     * - "the_geom" is always first, and used for the geometry attribute name
     * - "the_geom" must be of type Point, MultiPoint, MuiltiLineString, MultiPolygon
     * - Attribute names are limited in length
     * - Not all data types are supported (example Timestamp represented as Date)
     *
     * Each data store has different limitations so check the resulting SimpleFeatureType.
     */
    System.out.println("SHAPE:" + SHAPE_TYPE);

    if (featureSource instanceof SimpleFeatureStore) {
      SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
      /*
       * SimpleFeatureStore has a method to add features from a
       * SimpleFeatureCollection object, so we use the ListFeatureCollection
       * class to wrap our list of features.
       */
      SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
      featureStore.setTransaction(transaction);
      try {
        featureStore.addFeatures(collection);
        transaction.commit();
      } catch (Exception problem) {
        problem.printStackTrace();
        transaction.rollback();
      } finally {
        transaction.close();
      }
      System.exit(0); // success!
    } else {
      System.out.println(typeName + " does not support read/write access");
      System.exit(1);
    }
  }

}